package cn.amossun.starter.mybatis.data.security.loader;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.amossun.starter.mybatis.data.security.cache.LocalCache;
import cn.amossun.starter.mybatis.data.security.core.FieldConditionContext;
import cn.amossun.starter.mybatis.data.security.properties.ResultMapConfiguration;
import cn.amossun.starter.mybatis.data.security.rule.MybatisDecryptRule;
import cn.amossun.starter.mybatis.data.security.runner.OrmConfigCacheRunner;
import com.google.common.cache.CacheLoader;
import com.google.common.util.concurrent.JdkFutureAdapters;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;

/**
 * @description:
 * @author: Amos.Sun
 * @DateTime: 2020/03/01 15:27
 **/
@Slf4j
@AllArgsConstructor
public class DecryptRuleCacheLoader extends CacheLoader<String, Object> {

    private final ThreadPoolTaskExecutor amosThreadPoolTaskExecutor;

    private final OrmConfigCacheRunner ormConfigCacheRunner;

    //Mybatis解密对象规则
    private final MybatisDecryptRule mybatisDecryptRule;

    @Override
    public Optional<Object> load(String objectClassName) throws Exception {
        log.info("Guava DecryptRuleCacheLoader load更新key: {} ", objectClassName);
        return generateCacheValue(objectClassName);
    }

    @Override
    public ListenableFuture<Object> reload(String objectClassName, Object oldValue) throws Exception {
        Future<Object> future = amosThreadPoolTaskExecutor.submit(new Callable<Object>() {
            @Override
            public Optional<Object> call() throws Exception {
                log.info("Guava DecryptRuleCacheLoader reload定时异步更新key: {}", objectClassName);
                return generateCacheValue(objectClassName);
            }
        });
        return JdkFutureAdapters.listenInPoolThread(future);
    }

    Optional<Object> generateCacheValue(String objectClassName) throws Exception {
        //异步获取redisMap配置 更新本地object缓存
        Object object = ormConfigCacheRunner.getCacheValue();
        if(object == null || !(object instanceof Optional)) {
            return Optional.empty();
        }
        Optional<Object> optional = (Optional<Object>)object;
        if(!optional.isPresent()) {
            return Optional.empty();
        }

        List<ResultMapConfiguration> resultMapConfigurationList = JSONUtil.toList(JSONUtil.parseArray(optional.get()), ResultMapConfiguration.class);
        if(CollectionUtil.isEmpty(resultMapConfigurationList)) {
            return Optional.empty();
        }
        for(ResultMapConfiguration resultMapConfiguration : resultMapConfigurationList) {
            if(ObjectUtil.isNotEmpty(resultMapConfiguration) && StrUtil.isNotEmpty(resultMapConfiguration.getObjectClassName())
                    && resultMapConfiguration.getObjectClassName().equalsIgnoreCase(objectClassName)) {

                if(isPresent(objectClassName)) {
                    //更新当前查询对象
                    Class objectClass = ClassUtil.loadClass(objectClassName); //Class.forName(objectClassName);
                    Object parameterObject = objectClass.newInstance();
                    mybatisDecryptRule.refreshDecryptRuleMap();
                    FieldConditionContext context = new FieldConditionContext(parameterObject, mybatisDecryptRule);
                    LocalCache.updateObjectFiledCache(objectClassName, context.buildDecryptFields());
                    log.info("更新本地本地缓存对象完成: {}.", objectClassName);
                    break;
                }

            }
        }
        return Optional.ofNullable(LocalCache.getObjectFiledCache(objectClassName));
    }

    private boolean isPresent(String name) {
        try {
            Thread.currentThread().getContextClassLoader().loadClass(name);
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

}
